# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2016, Matt Layman

from tap.adapter import Adapter
from tap.directive import Directive
from tap.i18n import _
from tap.line import Result


class Rules(object):
    def __init__(self, filename, suite):
        self._filename = filename
        self._suite = suite
        self._lines_seen = {'plan': [], 'test': 0, 'version': []}

    def check(self, final_line_count):
        """Check the status of all provided data and update the suite."""
        if self._lines_seen['version']:
            self._process_version_lines()
        self._process_plan_lines(final_line_count)

    def _process_version_lines(self):
        """Process version line rules."""
        if len(self._lines_seen['version']) > 1:
            self._add_error(_('Multiple version lines appeared.'))
        elif self._lines_seen['version'][0] != 1:
            self._add_error(_('The version must be on the first line.'))

    def _process_plan_lines(self, final_line_count):
        """Process plan line rules."""
        if not self._lines_seen['plan']:
            self._add_error(_('Missing a plan.'))
            return

        if len(self._lines_seen['plan']) > 1:
            self._add_error(_('Only one plan line is permitted per file.'))
            return

        plan, at_line = self._lines_seen['plan'][0]
        if not self._plan_on_valid_line(at_line, final_line_count):
            self._add_error(
                _('A plan must appear at the beginning or end of the file.')
            )
            return

        if plan.expected_tests != self._lines_seen['test']:
            self._add_error(
                _(
                    'Expected {expected_count} tests ' 'but only {seen_count} ran.'
                ).format(
                    expected_count=plan.expected_tests,
                    seen_count=self._lines_seen['test'],
                )
            )

    def _plan_on_valid_line(self, at_line, final_line_count):
        """Check if a plan is on a valid line."""
        # Put the common cases first.
        if at_line == 1 or at_line == final_line_count:
            return True

        # The plan may only appear on line 2 if the version is at line 1.
        after_version = (
            self._lines_seen['version']
            and self._lines_seen['version'][0] == 1
            and at_line == 2
        )
        if after_version:
            return True

        return False

    def handle_bail(self, bail):
        """Handle a bail line."""
        self._add_error(_('Bailed: {reason}').format(reason=bail.reason))

    def handle_file_does_not_exist(self):
        """Handle a test file that does not exist."""
        self._add_error(_('{filename} does not exist.').format(filename=self._filename))

    def handle_skipping_plan(self, skip_plan):
        """Handle a plan that contains a SKIP directive."""
        skip_line = Result(True, None, skip_plan.directive.text, Directive('SKIP'))
        self._suite.addTest(Adapter(self._filename, skip_line))

    def saw_plan(self, plan, at_line):
        """Record when a plan line was seen."""
        self._lines_seen['plan'].append((plan, at_line))

    def saw_test(self):
        """Record when a test line was seen."""
        self._lines_seen['test'] += 1

    def saw_version_at(self, line_counter):
        """Record when a version line was seen."""
        self._lines_seen['version'].append(line_counter)

    def _add_error(self, message):
        """Add an error test to the suite."""
        error_line = Result(False, None, message, Directive(''))
        self._suite.addTest(Adapter(self._filename, error_line))
